{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit GridItems;

interface

uses Classes, AHMTypes, CaptionedObject, UIObjects, GridObjects, XMLHelper;

{$IFDEF DESIGNER}
const
  MODIFY_FONTS    = 'Edit Fonts';
  MODIFY_CAPTIONS = 'Edit Captions';

  // Property categories and hints for designer
  CAT_DESIGN = 'Design';
  CAT_APPEARANCE = 'Appearance';
  CAT_GRID = 'Grid';
  HINT_FONTS = 'defines fonts for each grid state (overrides caption font)';
  HINT_CAPTIONS = 'defines captions for each grid state (overrides default caption)';
  HINT_CAPTION = 'defines default caption for all grid states';
  HINT_CAPTIONX = 'specifies horizontal offset for the caption position';
  HINT_CAPTIONY = 'specifies vertical offset for the caption position';
{$ENDIF}

type
  TAHMGridItem = class(TAHMUICaptionedDisplayObject)
  private
    FStateFonts: TAHMUIStateObjectList;
    FStateCaptions: TAHMUIStateObjectList;
  protected
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    destructor Destroy; override;
    function ModifyActions: Integer; override;
    function Modify(Action: Integer): Boolean; override;
    function ModifyCaption(Action: Integer): String; override;
    procedure Render(Control: TObject; GridObj: TAHMGridObject; CustomText: String;
                     iX, iY, iWidth, iHeight: Single; Clip: Boolean = False); overload;
    procedure Render(Control: TObject; ItemState: TAHMControlState; ItemChecked: Boolean;
                     CustomText: String; iX, iY, iWidth, iHeight: Single; Clip: Boolean); overload;
    function RenderPreview: Boolean; override;
    procedure SeedObject; override;
    procedure Assign(Source: TPersistent); override;
    procedure SaveXMLBody(Lines: TStrings); override;
    procedure ParseCommonXML(XML: TAHMXMLHelper); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property StateFonts: TAHMUIStateObjectList read FStateFonts;
    property StateCaptions: TAHMUIStateObjectList read FStateCaptions;
    // Publish inherited properties
    property Caption;
    property VirtualWidth;
    property VirtualHeight;
    property VirtualCaptionX;
    property VirtualCaptionY;
  end;

  TAHMGridItemList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddGridItem(const InheritFrom, NewName: String): TAHMGridItem;
    function GetGridItem(const Name: String): TAHMGridItem;
    procedure PopulateDefaultObjects; override;
  end;

implementation

uses SysUtils, ApplicationManager, Canvas, BaseControls, UIControls, Captions,
     Fonts, Screen, Colour, {$IFDEF DESIGNER}DesignerStateList,{$ENDIF}
     MetaConsts, TagNames, XMLTags, ErrorCodes;


constructor TAHMGridItemList.Create;
begin
  inherited Create;

  XMLTagName := TAG_GRID_ITEMS;
  ChildClass := TAHMGridItem;
end;

function TAHMGridItemList.AddGridItem(const InheritFrom, NewName: String): TAHMGridItem;
begin
  Result := TAHMGridItem(Inherit(InheritFrom, NewName));
end;

function TAHMGridItemList.GetGridItem(const Name: String): TAHMGridItem;
begin
  Result := TAHMGridItem(GetObject(Name));
end;

procedure TAHMGridItemList.PopulateDefaultObjects;
begin
  with ApplicationManagerInstance.ThemeManager, DefaultObject as TAHMGridItem do
  begin
    VirtualCaptionX := DEFAULT_CTRL_WIDTH div 3;
    VirtualCaptionY := DEFAULT_CTRL_HEIGHT div 3;
    StateCaptions.AddObject(STATE_VAL_IDLE, ThemeObjects[otCaptions].DefaultObject);
  end;
end;

//----------------------------------------------------------------------------//

constructor TAHMGridItem.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_GRID_ITEM;
  ObjectType := otGridItems;
  ImageIndex := Ord(otGridItems);

  FStateFonts := TAHMUIStateObjectList.Create;
  FStateFonts.ObjectTypes := [otFonts];
  FStateFonts.Owner := Self;
  FStateCaptions := TAHMUIStateObjectList.Create;
  FStateCaptions.ObjectTypes := [otCaptions];
  FStateCaptions.Owner := Self;

{$IFDEF DESIGNER}
  FStateFonts.DisplayClassName := 'Grid font';
  FStateCaptions.DisplayClassName := 'Grid caption';
{$ENDIF}

  // Set default property values
  ClearProperties;
end;

destructor TAHMGridItem.Destroy;
begin
  FStateFonts.Free;
  FStateCaptions.Free;

  inherited Destroy;
end;

procedure TAHMGridItem.ClearProperties;
begin
  inherited;

  // Clear custom grid item properties
  FStateFonts.Clear;
  FStateCaptions.Clear;
end;

function TAHMGridItem.ModifyActions: Integer;
begin
  // Grid item supports editing fonts and captions
  Result := 2;
end;

function TAHMGridItem.Modify(Action: Integer): Boolean;
begin
{$IFDEF DESIGNER}
  case Action of
    0: // Edit fonts with list dialog
       Result := TfrmObjectStateList.ShowList(FStateFonts);
    1: // Edit captions with list dialog
       Result := TfrmObjectStateList.ShowList(FStateCaptions);
    else Result := False;
  end;

  if Result then DoModified;
{$ELSE}
  Result := False;
{$ENDIF}
end;

function TAHMGridItem.ModifyCaption(Action: Integer): String;
begin
{$IFDEF DESIGNER}
  case Action of
    0: Result := MODIFY_FONTS;
    1: Result := MODIFY_CAPTIONS;
    else Result := '';
  end;
{$ELSE}
  Result := '';
{$ENDIF}
end;

procedure TAHMGridItem.Render(Control: TObject; GridObj: TAHMGridObject; CustomText: String;
                              iX, iY, iWidth, iHeight: Single; Clip: Boolean = False);
begin
  // Delegate to method below using grid object properties
  Render(Control, GridObj.State, GridObj.Checked, CustomText, iX, iY, iWidth, iHeight, Clip);
end;

procedure TAHMGridItem.Render(Control: TObject; ItemState: TAHMControlState; ItemChecked: Boolean;
                              CustomText: String; iX, iY, iWidth, iHeight: Single; Clip: Boolean);
var
  oStateCaption: TAHMCaption;
  oStateFont: TAHMFont;
  iCapX, iCapY: Single;
begin
  with Control as TAHMControl do
  begin
    oStateCaption := TAHMCaption(FStateCaptions.GetStateObject(ItemState, ItemChecked));
    oStateFont := TAHMFont(FStateFonts.GetStateObject(ItemState, ItemChecked));

    // Determine which font we should use - font overrides caption
    if not Assigned(oStateFont) then
      if Assigned(oStateCaption) then oStateFont := oStateCaption.Font
      else if Assigned(Caption) then oStateFont := Caption.Font;

    // Render text using custom caption if found
    if not Assigned(oStateCaption) then oStateCaption := Caption;

    // Render grid item text over other images
    if Assigned(oStateCaption) then
    begin
      // Tweak caption position a little to account for flipping
      if HorizontalFlip then iCapX := iWidth - CaptionX - oStateCaption.Width
                        else iCapX := CaptionX;
      if VerticalFlip then iCapY := iHeight - CaptionY - oStateCaption.Height
                      else iCapY := CaptionY;

      oStateCaption.RenderCustomFont(Control, oStateFont, CustomText,
                                     iX + iCapX, iY + iCapY,
                                     iWidth - iCapX,
                                     iHeight - iCapY, Clip);
    end;
  end;
end;

function TAHMGridItem.RenderPreview: Boolean;
{$IFDEF DESIGNER}
var
  Control: TAHMControl;
  X, Y, BoxW, BoxH, GapX, GapY: Single;
{$ENDIF}
begin
{$IFDEF DESIGNER}
  // Default preview rendering implementation
  with ScreenInstance, ScreenInstance.Viewport do
  begin
    Canvas.DesignerBackground(ViewportRect);
    Canvas.PenColour := TAHMCanvasColour.Black;
    Canvas.PenWidth := 0.005;
    with ApplicationManagerInstance.UIManager do
      Control := GetPreviewControl(TAHMControl);
    Control.Viewport := ScreenInstance.Viewport;

    // Initialise dimensions for rendering preview grid
    GapX := 0.05; GapY := 0.05; X := GapX; Y := GapY; BoxW := 0.4; BoxH := 0.15;

    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csDisabled, False, 'Disabled', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csIdle, False, 'Idle', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csFocused, False, 'Focused', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csPressed, False, 'Pressed', X, Y, BoxW, BoxH, False);
    Y := GapY;
    X := X + BoxW + GapX;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csDisabled, True, 'Disabled, Checked', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csIdle, True, 'Idle, Checked', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csFocused, True, 'Focused, Checked', X, Y, BoxW, BoxH, False);
    Y := Y + BoxH + GapY;
    Canvas.Rectangle(MakeRectF(X, Y, BoxW, BoxH), nil);
    Render(Control, csPressed, True, 'Pressed, Checked', X, Y, BoxW, BoxH, False);
  end;
{$ENDIF}
  Result := True;
end;

procedure TAHMGridItem.SeedObject;
begin
{$IFDEF DESIGNER}
  if Ancestor = nil then
    with ApplicationManagerInstance.ThemeManager.VisualDescriptor do
    begin
      VirtualCaptionX := DEFAULT_CTRL_WIDTH div 3;
      VirtualCaptionY := DEFAULT_CTRL_HEIGHT div 3;
      StateCaptions.AddObject(STATE_VAL_IDLE, CaptionList.GetCaption(DEFAULT_OBJECT_NAME));
    end;
{$ENDIF}
end;

procedure TAHMGridItem.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMGridItem do
  begin
    Self.StateFonts.Assign(StateFonts);
    Self.StateCaptions.Assign(StateCaptions);
  end;
end;

procedure TAHMGridItem.SaveXMLBody(Lines: TStrings);
var
  oAncs: TAHMGridItem;
  oFont, oCaption: TAHMUIObject;
  i: Integer;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMGridItem(ObjectToCompare);

  // Only populate attributes that differ from our ancestor
  if (Caption <> nil) and (Caption <> oAncs.Caption) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_CAPTION+SPC+ATTR_NAME+ATB + Caption.Name + ATC);
    // Do we need to store position?
    if ((VirtualCaptionX <> 0) and (VirtualCaptionX <> oAncs.VirtualCaptionX)) or
       ((VirtualCaptionY <> 0) and (VirtualCaptionY <> oAncs.VirtualCaptionY)) then
      Lines.Add(TABS[4] + TGB+TAG_POSITION+TGC+ IntToStr(VirtualCaptionX) + ',' +
                IntToStr(VirtualCaptionY) + TGE+TAG_POSITION+TGC);
    Lines.Add(TABS[3] + TGE+TAG_CAPTION+TGC);
  end;

  // Do we need to store states?
  if FStateFonts.IsDifferent(oAncs.StateFonts) or
     FStateCaptions.IsDifferent(oAncs.StateCaptions) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_STATES+TGC);
    for i := 0 to High(ControlStateNames) do
    begin
      oFont := FStateFonts.GetState(ControlStateNames[i]);
      oCaption := FStateCaptions.GetState(ControlStateNames[i]);

      if Assigned(oFont) or Assigned(oCaption) then
      begin
        Lines.Add(TABS[4] + TGB+TAG_STATE+SPC+ATTR_NAME+ATB + EncodeXML(ControlStateNames[i]) + ATC);
        if Assigned(oCaption) then Lines.Add(TABS[5] + oCaption.XMLReference);
        if Assigned(oFont) then Lines.Add(TABS[5] + oFont.XMLReference);
        Lines.Add(TABS[4] + TGE+TAG_STATE+TGC);
      end;
    end;
    Lines.Add(TABS[3] + TGE+TAG_STATES+TGC);
  end;
end;

procedure TAHMGridItem.ParseCommonXML(XML: TAHMXMLHelper);
var
  sCaption, sState, sDest: String;
  iX, iY: Integer;
  PropsNode, DetailNode, StateNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  PropsNode := XML.CurrentNode;
  DetailNode := nil;

  with ApplicationManagerInstance.ThemeManager.VisualDescriptor do
    if XML.CurrentElement = TAG_CAPTION then
    begin
      // Handle grid captions
      sCaption := XML.GetAttribute(ATTR_NAME);
      if sCaption <> '' then Caption := CaptionList.GetCaption(sCaption);

      while XML.NextElement(PropsNode, DetailNode) do
      begin
        // Check for caption position
        if XML.CurrentElement = TAG_POSITION then
        begin
          if XML.GetCoordsValue(iX, iY) then
          begin
            VirtualCaptionX := iX;
            VirtualCaptionY := iY;
          end
          else
            LogParsingError(THM_PRS_VIS_MALFORMED_GD_POSITION_TAGS, Self);
        end;
      end;
    end
    else if XML.CurrentElement = TAG_STATES then
    begin
      // Clear current states before parsing
      StateFonts.Clear;
      StateCaptions.Clear;

      // Handle grid states
      while XML.FindElement(PropsNode, DetailNode, TAG_STATE) do
      begin
        sState := XML.GetAttribute(ATTR_NAME);
        StateNode := nil;

        // We may have multiple state properties
        while XML.NextElement(DetailNode, StateNode) do
        begin
          // What type of object do we want to use for this state?
          sDest := XML.GetAttribute(ATTR_NAME);
          if XML.CurrentElement = TAG_FONT then
            StateFonts.AddObject(sState, FontList.GetFont(sDest))
          else if XML.CurrentElement = TAG_CAPTION then
            StateCaptions.AddObject(sState, CaptionList.GetCaption(sDest))
          else
            LogParsingError(THM_PRS_VIS_UNSUPPORTED_GRID_STATE_TYPE, Self);
        end;
      end;
    end;
end;

procedure TAHMGridItem.ParseFromXML(XML: TAHMXMLHelper);
var
  GridNode, PropsNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  GridNode := XML.CurrentNode;
  PropsNode := nil;

  // Use above method to process common grid elements
  while XML.NextElement(GridNode, PropsNode) do
    ParseCommonXML(XML);
end;

procedure TAHMGridItem.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom grid item properties
  Properties.Add('Fonts', 'StateFonts', CAT_APPEARANCE, HINT_FONTS);
  Properties.Add('Captions', 'StateCaptions', CAT_APPEARANCE, HINT_CAPTIONS);
  Properties.Add('Default Caption', 'Caption', CAT_APPEARANCE, HINT_CAPTION);
  Properties.Add('Caption X', 'VirtualCaptionX', CAT_DESIGN, HINT_CAPTIONX);
  Properties.Add('Caption Y', 'VirtualCaptionY', CAT_DESIGN, HINT_CAPTIONY);
{$ENDIF}
end;

end.
